package com.xuexiang.xui_lib.component.progress.ratingbar;

import static com.xuexiang.xui_lib.component.progress.ratingbar.PartialView.LABEL;

import com.xuexiang.xui_lib.ResourceTable;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import org.jetbrains.annotations.Nullable;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 星级评分控件
 *
 * @author xuexiang
 * @since 2019-03-26 下午10:50
 */
public class RatingBar extends DirectionalLayout implements IRatingBar, Component.TouchEventListener {
    private static final int NUM5 = 5;
    private static final int NUM20 = 20;
    private static final int NUM1 = -1;
    private static final float NUM0_1 = 0.1f;
    private static final float NUM10 = 10f;
    protected List<PartialView> mPartialViews;
    private PixelMap mEmptyDrawable;
    private PixelMap mFilledDrawable;
    private OnRatingChangeListener mOnRatingChangeListener;

    private int mNumStars;
    private int mPadding = NUM20;
    private int mStarWidth;
    private int mStarHeight;
    private float mMinimumStars = 0;
    private float mRating = NUM1;
    private float mStepSize = 1f;
    private float mPreviousRating = 0;

    private boolean mIsIndicator = false;
    private boolean mIsScrollable = true;
    private boolean mIsClickable = true;
    private boolean mClearRatingEnabled = true;

    private float mStartX;
    private float mStartY;

    /**
     * RatingBar
     *
     * @param context
     */
    public RatingBar(Context context) {
        this(context, null);
    }

    /**
     * RatingBar
     *
     * @param context
     * @param attrs
     */
    public RatingBar(Context context, @Nullable AttrSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * RatingBar
     *
     * @param context context
     * @param attrs attributes from XML => app:mainText="mainText"
     * @param defStyleAttr attributes from default style (Application theme or activity theme)
     */
    public RatingBar(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, "");

        final float rating = AttrValueUtil.get(attrs, "srb_rating", 0.0f);
        mNumStars = AttrValueUtil.get(attrs, "srb_numStars", 0);
        mStepSize = AttrValueUtil.get(attrs, "srb_stepSize", mStepSize);
        mMinimumStars = AttrValueUtil.get(attrs, "srb_minimumStars", mMinimumStars);
        mPadding = AttrValueUtil.get(attrs, "srb_starPadding", mPadding);
        mStarWidth = AttrValueUtil.get(attrs, "srb_starWidth", 0);
        mStarHeight = AttrValueUtil.get(attrs, "srb_starHeight", 0);

        try {
            Element mEmptyD = attrs.getAttr("srb_drawableEmpty").get().getElement();
            mEmptyDrawable = ((PixelMapElement) mEmptyD).getPixelMap();
        } catch (Exception ex) {
            HiLog.info(LABEL, ex.toString());
        }
        try {
            Element mFilledD = attrs.getAttr("srb_drawableFilled").get().getElement();
            mFilledDrawable = ((PixelMapElement) mFilledD).getPixelMap();
        } catch (Exception ex) {
            HiLog.info(LABEL, ex.toString());
        }

        mIsIndicator = AttrValueUtil.get(attrs, "srb_isIndicator", mIsIndicator);
        mIsScrollable = AttrValueUtil.get(attrs, "srb_scrollable", mIsScrollable);
        mIsClickable = AttrValueUtil.get(attrs, "srb_clickable", mIsClickable);
        mClearRatingEnabled = AttrValueUtil.get(attrs, "srb_clearRatingEnabled", mClearRatingEnabled);

        setOrientation(HORIZONTAL);
        verifyParamsValue();
        initRatingView();
        setRating(rating);
        setTouchEventListener(this);
    }

    private void verifyParamsValue() {
        if (mNumStars <= 0) {
            mNumStars = NUM5;
        }

        if (mPadding < 0) {
            mPadding = 0;
        }

        if (mEmptyDrawable == null) {
            mEmptyDrawable = getDrawableResource(ResourceTable.Media_srb_ic_empty);
        }

        if (mFilledDrawable == null) {
            mFilledDrawable = getDrawableResource(ResourceTable.Media_srb_ic_filled);
        }

        if (mStepSize > 1.0f) {
            mStepSize = 1.0f;
        } else if (mStepSize < NUM0_1) {
            mStepSize = NUM0_1;
        }

        mMinimumStars = RatingBarUtils.getValidMinimumStars(mMinimumStars, mNumStars, mStepSize);
    }

    /**
     * setScaleOk
     */
    protected void setScaleOk() {
        if (mPartialViews != null && mPartialViews.size() > 0) {
            for (PartialView partialView : mPartialViews) {
                partialView.setScale(true);
            }
        }
    }

    private void initRatingView() {
        mPartialViews = new ArrayList<>();

        for (int i = 1; i <= mNumStars; i++) {
            PartialView partialView = getPartialView(i, mStarWidth, mStarHeight,
                mPadding, mFilledDrawable, mEmptyDrawable);
            addComponent(partialView);

            mPartialViews.add(partialView);
        }
    }

    private PartialView getPartialView(final int partialViewId, int starWidth, int starHeight, int padding,
                                       PixelMap filledDrawable, PixelMap emptyDrawable) {
        PartialView partialView = new PartialView(getContext(), partialViewId, starWidth, starHeight, padding);
        partialView.setFilledDrawable(filledDrawable);
        partialView.setEmptyDrawable(emptyDrawable);
        return partialView;
    }

    /**
     * Retain this method to let other RatingBar can custom their decrease animation.
     */
    protected void emptyRatingBar() {
        fillRatingBar(0);
    }

    /**
     * Use {maxIntOfRating} because if the rating is 3.5
     * the view which id is 3 also need to be rb_ic_filled.
     *
     * @param rating
     */
    protected void fillRatingBar(final float rating) {
        for (PartialView partialView : mPartialViews) {
            HiLog.info(LABEL, "setPartialFilled===>" + partialView.getTag());
            int ratingViewId = (int) partialView.getTag();
            double maxIntOfRating = Math.ceil(rating);

            if (ratingViewId > maxIntOfRating) {
                partialView.setEmpty();
                continue;
            }

            if (ratingViewId == maxIntOfRating) {
                partialView.setPartialFilled(rating);
            } else {
                partialView.setFilled();
            }
        }
    }

    @Override
    public void setNumStars(int numStars) {
        if (numStars <= 0) {
            return;
        }

        mPartialViews.clear();
        removeAllComponents();

        mNumStars = numStars;
        initRatingView();
    }

    @Override
    public int getNumStars() {
        return mNumStars;
    }

    @Override
    public void setRating(float rating) {
        if (rating > mNumStars) {
            rating = mNumStars;
        }

        if (rating < mMinimumStars) {
            rating = mMinimumStars;
        }

        if (mRating == rating) {
            return;
        }

        mRating = rating;
        if (mOnRatingChangeListener != null) {
            mOnRatingChangeListener.onRatingChange(this, mRating);
        }

        fillRatingBar(rating);
    }

    @Override
    public float getRating() {
        return mRating;
    }

    @Override
    // Unit is pixel
    public void setStarWidth(int starWidth) {
        mStarWidth = starWidth;
        for (PartialView partialView : mPartialViews) {
            partialView.setStarWidth(starWidth);
        }
    }

    @Override
    public int getStarWidth() {
        return mStarWidth;
    }

    @Override
    // Unit is pixel
    public void setStarHeight(int starHeight) {
        mStarHeight = starHeight;
        for (PartialView partialView : mPartialViews) {
            partialView.setStarHeight(starHeight);
        }
    }

    @Override
    public int getStarHeight() {
        return mStarHeight;
    }

    @Override
    public void setStarPadding(int ratingPadding) {
        if (ratingPadding < 0) {
            return;
        }

        mPadding = ratingPadding;

        for (PartialView partialView : mPartialViews) {
            partialView.setPadding(mPadding, mPadding, mPadding, mPadding);
        }
    }

    @Override
    public int getStarPadding() {
        return mPadding;
    }

    @Override
    public void setEmptyDrawableRes(int res) {
        setEmptyDrawable(getDrawableResource(res));
    }

    @Override
    public void setFilledDrawableRes(int res) {
        setFilledDrawable(getDrawableResource(res));
    }

    @Override
    public void setEmptyDrawable(PixelMap drawable) {
        mEmptyDrawable = drawable;

        for (PartialView partialView : mPartialViews) {
            partialView.setEmptyDrawable(drawable);
        }
    }

    @Override
    public void setFilledDrawable(PixelMap drawable) {
        mFilledDrawable = drawable;

        for (PartialView partialView : mPartialViews) {
            partialView.setFilledDrawable(drawable);
        }
    }

    @Override
    public void setMinimumStars(float minimumStars) {
        mMinimumStars = RatingBarUtils.getValidMinimumStars(minimumStars, mNumStars, mStepSize);
    }

    @Override
    public boolean isIndicator() {
        return mIsIndicator;
    }

    @Override
    public void setIsIndicator(boolean indicator) {
        mIsIndicator = indicator;
    }

    @Override
    public boolean isScrollable() {
        return mIsScrollable;
    }

    @Override
    public void setScrollable(boolean scrollable) {
        mIsScrollable = scrollable;
    }

    @Override
    public boolean isClickable() {
        return mIsClickable;
    }

    @Override
    public void setClickable(boolean clickable) {
        this.mIsClickable = clickable;
    }

    @Override
    public void setClearRatingEnabled(boolean enabled) {
        this.mClearRatingEnabled = enabled;
    }

    @Override
    public boolean isClearRatingEnabled() {
        return mClearRatingEnabled;
    }

    @Override
    public float getStepSize() {
        return mStepSize;
    }

    @Override
    public void setStepSize(float stepSize) {
        this.mStepSize = stepSize;
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (isIndicator()) {
            return false;
        }

        MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());

        float eventX = point.getX();
        float eventY = point.getY();
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                mStartX = eventX;
                mStartY = eventY;
                mPreviousRating = mRating;
                break;
            case TouchEvent.POINT_MOVE:
                if (!isScrollable()) {
                    return false;
                }

                handleMoveEvent(eventX);
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                if (!RatingBarUtils.isClickEvent(mStartX, mStartY, touchEvent) || !isClickable()) {
                    return false;
                }

                handleClickEvent(eventX);
                break;
            default:
                break;
        }
        return true;
    }

    private void handleMoveEvent(float eventX) {
        for (PartialView partialView : mPartialViews) {
            if (eventX < partialView.getWidth() / NUM10 + (mMinimumStars * partialView.getWidth())) {
                setRating(mMinimumStars);
                return;
            }

            if (!isPositionInRatingView(eventX, partialView)) {
                continue;
            }

            float rating = RatingBarUtils.calculateRating(partialView, mStepSize, eventX);

            if (mRating != rating) {
                setRating(rating);
            }
        }
    }

    private void handleClickEvent(float eventX) {
        for (PartialView partialView : mPartialViews) {
            if (!isPositionInRatingView(eventX, partialView)) {
                continue;
            }

            float rating = mStepSize == 1 ? (float) partialView.getTag()
                : RatingBarUtils.calculateRating(partialView, mStepSize, eventX);

            if (mPreviousRating == rating && isClearRatingEnabled()) {
                setRating(mMinimumStars);
            } else {
                setRating(rating);
            }
            break;
        }
    }

    private boolean isPositionInRatingView(float eventX, Component ratingView) {
        return eventX > ratingView.getLeft() && eventX < ratingView.getRight();
    }

    public void setOnRatingChangeListener(OnRatingChangeListener onRatingChangeListener) {
        mOnRatingChangeListener = onRatingChangeListener;
    }

    /**
     * OnRatingChangeListener
     *
     * @since 2021-04-25
     */
    public interface OnRatingChangeListener {
        /**
         * onRatingChange
         *
         * @param ratingBar
         * @param rating
         */
        void onRatingChange(RatingBar ratingBar, float rating);
    }

    private PixelMap getDrawableResource(int var) {
        return getPixelMap(var);
    }

    private PixelMap getPixelMap(int drawableId) {
        InputStream drawableInputStream = null;
        try {
            drawableInputStream = getContext().getResourceManager().getResource(drawableId);
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            sourceOptions.formatHint = "image/png";

            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            decodingOptions.desiredSize = new Size(mStarWidth, mStarHeight);
            decodingOptions.desiredRegion = new Rect(0, 0, 0, 0);
            decodingOptions.desiredPixelFormat = PixelFormat.ARGB_8888;
            ImageSource imageSource = ImageSource.create(drawableInputStream, sourceOptions);
            PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);
            return pixelMap;
        } catch (Exception e) {
            HiLog.info(LABEL, e.toString());
        } finally {
            try {
                if (drawableInputStream != null) {
                    drawableInputStream.close();
                }
            } catch (Exception e) {
                HiLog.info(LABEL, e.toString());
            }
        }
        return null;
    }
}